#include "Spark/Net/Socket.h"
#include "Spark/Utils/Logger.h"
#include <unistd.h>
#include <map>

static const std::map<SO_, std::string> optionstr{
    {SO_::REUSEPORT, "SO_REUSEPORT"},
    {SO_::REUSEADDR, "SO_REUSEADDR"},
    {SO_::LINGER, "SO_LINGER"},
    {SO_::SNDBUF, "SO_SNDBUF"},
    {SO_::RCVBUF, "SO_RCVBUF"},
    {SO_::KEEPALIVE, "SO_KEEPALIVE"}};

Socket::~Socket()
{
    LOG_TRACE << "Socket: " << sockfd_ << " closed";
    ::close(sockfd_);
}

int Socket::createSocket(sa_family_t family)
{
    int fd = ::socket(family,
                      SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
                      IPPROTO_TCP);
    if (fd < 0)
    {
        LOG_SYSERR << "Socket::createSocket(" << family << ") failed";
        throw std::system_error(errno,
                                std::system_category(),
                                "socket create failed");
    }
    return fd;
}

SocketAddr Socket::getLocalAddr(int sockfd)
{
    socklen_t len = kIn6AddrLen;
    sockaddr sa{};
    if (::getsockname(sockfd, &sa, &len) < 0)
    {
        LOG_SYSERR << "getsockname(fd=" << sockfd << ") failed";
    }
    return SocketAddr(sa);
}

SocketAddr Socket::getPeerAddr(int sockfd)
{
    socklen_t len = kIn6AddrLen;
    sockaddr sa{};
    if (::getpeername(sockfd, &sa, &len) < 0)
    {
        LOG_SYSERR << "getpeername(fd=" << sockfd << ") failed";
    }
    return SocketAddr(sa);
}

void Socket::connect(SocketAddr* peeraddr)
{
    assert(sockfd_ > 0);
    socklen_t socklen =
        peeraddr->family() == AF_INET ? kInAddrLen : kIn6AddrLen;
    if (::connect(sockfd_, peeraddr->sa(), socklen) < 0)
    {
        LOG_SYSERR << "connect(" << peeraddr->toStringPort() << ")failed";
    }
}

int Socket::connect(int sockfd, SocketAddr* peeraddr)
{
    assert(sockfd > 0);
    socklen_t socklen =
        peeraddr->family() == AF_INET ? kInAddrLen : kIn6AddrLen;
    return ::connect(sockfd, peeraddr->sa(), socklen);
}

int Socket::getSockErrno(int fd)
{
    int optval;
    socklen_t optlen = sizeof(optval);
    if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
    {
        LOG_SYSERR << "getsockopt(fd=" << fd << ") error";
        return errno;
    }
    return optval;
}

int Socket::getSockErrno()
{
    return getSockErrno(sockfd_);
}

bool Socket::isSelfConnected(int fd)
{
    return getLocalAddr(fd) == getPeerAddr(fd);
}

void Socket::bind(SocketAddr* addr)
{
    assert(sockfd_ > 0);
    assert(addr->sa()->sa_family != 0);
    socklen_t len = addr->family() == AF_INET ? kInAddrLen : kIn6AddrLen;
    if (::bind(sockfd_, addr->sa(), len) < 0)
    {
        LOG_SYSERR << "bind(" << addr->toStringPort() << ") failed";
        std::system_error(errno, std::system_category(), "bind");
    }
}

void Socket::listen()
{
    assert(sockfd_ > 0);
    if (::listen(sockfd_, SOMAXCONN) < 0)
    {
        LOG_SYSERR << "listen failed";
        std::system_error(errno, std::system_category(), "listen");
    }
}

int Socket::accept(SocketAddr* peeraddr)
{
    socklen_t len{};
    int clientfd =
        accept4(sockfd_, peeraddr->sa(), &len, SOCK_NONBLOCK | SOCK_CLOEXEC);
    return clientfd;
}

void Socket::closeWrite()
{
    if (::shutdown(sockfd_, SHUT_WR) < 0)
        LOG_SYSERR << "sockfd:" << sockfd_ << " shutdown() error";
}

void Socket::setSocketOption(SO_ option, socklen_t optval)
{
    int optname = static_cast<int>(option);
    int ret =
        ::setsockopt(sockfd_, SOL_SOCKET, optname, &optval, sizeof(optval));
    if (ret < 0)
    {
        LOG_SYSERR << optionstr.at(option) << " set failed";
    }
}

void Socket::setTCPNoDelay(socklen_t optval)
{
    int ret = ::setsockopt(
        sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
    if (ret < 0)
    {
        LOG_SYSERR << "TCP_NODELAY set failed";
    }
}

ssize_t Socket::readn(char* buffer, size_t len)
{
    return ::read(sockfd_, buffer, len);
}

bool Socket::getTcpInfo(tcp_info* tcpi) const
{
    socklen_t len = sizeof(tcp_info);
    memset(tcpi, 0, len);
    return ::getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpi, &len) == 0;
}

bool Socket::getTcpInfoString(char* buf, int len) const
{
    tcp_info tcpi;
    bool ok = getTcpInfo(&tcpi);
    if (ok)
    {
        snprintf(buf,
                 len,
                 "unrecovered=%u "
                 "rto=%u ato=%u snd_mss=%u rcv_mss=%u "
                 "lost=%u retrans=%u rtt=%u rttvar=%u "
                 "sshthresh=%u cwnd=%u total_retrans=%u",
                 tcpi.tcpi_retransmits,  // Number of unrecovered [RTO] timeouts
                 tcpi.tcpi_rto,          // Retransmit timeout in usec
                 tcpi.tcpi_ato,          // Predicted tick of soft clock in usec
                 tcpi.tcpi_snd_mss,
                 tcpi.tcpi_rcv_mss,
                 tcpi.tcpi_lost,     // Lost packets
                 tcpi.tcpi_retrans,  // Retransmitted packets out
                 tcpi.tcpi_rtt,      // Smoothed round trip time in usec
                 tcpi.tcpi_rttvar,   // Medium deviation
                 tcpi.tcpi_snd_ssthresh,
                 tcpi.tcpi_snd_cwnd,
                 tcpi.tcpi_total_retrans);  // Total retransmits for entire
                                            // connection
    }
    return ok;
}
